#include "player.h"
#include "core_io.h"

#include <math.h>
#include <SoundTouchDLL.h>

float Player::k = 0.69314718056f;

/* -------------------------- fmod dsp soundtouch unit ----------------------------------------- */
short           pitch_BufOut[409600];
short           pitch_BufOutTmp[4096];
int             pitch_BufOutPosWrite = 0;
int             pitch_BufOutPosRead = 0;
bool            mono = false;
HANDLE          st_handle = soundtouch_createInstance();

/* the dsp callback function */
void* F_CALLBACKAPI dsp_st_callback
        ( void* org_buf, void* new_buf, int len, void* userdata )
{
    short *srcleft, *srcright;
    int l, r, n_samples = 0, i = 0;

    srcleft     = ( ( signed short* ) new_buf );
    srcright    = ( ( signed short* ) new_buf ) + 1;

    if( !mono ) len *= 2;

    // process
    soundtouch_putSamples( st_handle, srcleft, len/2 );
    do
    {
        // receive processed audio data
        n_samples = soundtouch_receiveSamples( st_handle, pitch_BufOutTmp, 2048 );

        // calculate processed audio data size in bytes
        for( i = 0; i < n_samples * 2; i += 2 )
        {
            pitch_BufOut[ pitch_BufOutPosWrite++ ] = pitch_BufOutTmp[ i ];
            pitch_BufOut[ pitch_BufOutPosWrite++ ] = pitch_BufOutTmp[ i + 1 ];

            if( pitch_BufOutPosWrite >= 409600 )
                pitch_BufOutPosWrite = 0;
        }
    }while( n_samples != 0 );

    // main loop
    for( int count = 0; count < len; count += 2 )
    {
        l = srcleft[ count ];
        r = srcright[ count ];

        if( pitch_BufOutPosWrite != pitch_BufOutPosRead )
        {
            l = pitch_BufOut[ pitch_BufOutPosRead++ ];
            r = pitch_BufOut[ pitch_BufOutPosRead++ ];

            if( pitch_BufOutPosRead == 409600 )
                pitch_BufOutPosRead = 0;
        }
        else
        {
            l = 0;
            r = 0;
        }

        // clip and write new values
        if      (l < -32768) l = -32768;
        else if (l >  32767) l =  32767;
        if      (r < -32768) r = -32768;
        else if (r >  32767) r =  32767;

        srcleft[ count ] = l;
        srcright[ count ] = r;
    }

    return new_buf;
}

/* the dsp unit build function */
FSOUND_DSPUNIT* dsp_create( FSOUND_STREAM* stream, int priority )
{
    FSOUND_SAMPLE* sample = FSOUND_Stream_GetSample( stream );
    unsigned int mode = FSOUND_Sample_GetMode( sample );
    mono = ( mode & 0x00000020 );

    soundtouch_setSampleRate( st_handle, 44100 );
    soundtouch_setChannels( st_handle, 2 );
    soundtouch_setTempo( st_handle, 1.0f );
    soundtouch_setRate( st_handle, 1.0f );
    soundtouch_setPitch( st_handle, 1.0f);

    return ( FSOUND_Stream_CreateDSP( stream, dsp_st_callback, priority, 0 ) );
}

// the most important function of the whole program
void set_pitch( float p )
{
    soundtouch_setPitch( st_handle, p );
}
/* --------------------------------------------------------------------------------------------- */

/* --------------------------- fmod callbacks and other stuff ---------------------------------- */
signed char F_CALLBACKAPI callback(FSOUND_STREAM *stream, void *buff, int len, void* userdata)
{
    // end of stream callback doesn't have a 'buff' value, if it doesn't it could be a sync point.
    if (buff) return FALSE;

    // end of stream
    Player* p = (Player*)userdata;
    if ( FSOUND_Stream_SetTime( stream, 0 ) == TRUE )
    {
        p->firstSynchronization = true;
        p->activeChannel = FSOUND_Stream_Play( 0, stream );
        FSOUND_SetMute( p->activeChannel, TRUE );
        p->SetVolume( p->vol / 255.0);
        p->Synchronize();
    }else
    {
        qDebug( "not resetted" );
    }
    return TRUE;
}
/* --------------------------------------------------------------------------------------------- */

/* -------------------------- instance managing ------------------------------------------------ */
Player* Player::instance = 0;

Player* Player::GetInstance( anime::Engine* _e )
{
    if(instance == 0)
    {
        instance = new Player( _e );
    }
    return instance;
}

void Player::DeleteInstance()
{
    if(instance != 0)
    {
        delete instance;
    }
}
/* --------------------------------------------------------------------------------------------- */

/* --------------------------- constructor and destructor -------------------------------------- */
Player::Player( anime::Engine* _e )
{
    FSOUND_Init(44100, 32, 0);
    song = 0;
    activeChannel = -1;
    activeStream = 0;
    segmentPlaying = -1;
    tempo = 1.0;
    pitch = 0;
    playing = false;
    firstPlay = true;
    synchronizing = false;
    loopsTracking = true;
    delayedSynchronization = false;
    normalPlayback = false;
    firstSynchronization = true;

    sleeper= new SleeperThread( this );
    sleeper->start();

    e = _e;
    vol = 0;
    vs = new VolSetter( this );
    vg = new SimplePropertyGetter< int >( &vol );
    om_ni = new OneMinus_NI< float >( new Linear_NI< float >(), true );
    volAnime = new Animation( e->fps, 400 );

    animeGraph.AddProperty( "vol", vg, vs );
    volId = animeGraph.AddInterpolator( volAnime->GetTime(), 0, 0, "vol", 0, 0, om_ni );
    volAnime->AddObserver( this );
    e->AddAnimation( volAnime );

    e->AddTask( this );
}

Player::~Player()
{
    sleeper->StopIt();
    while( sleeper->isRunning() );
    delete sleeper;

    delete vg;
    delete vs;
    delete volAnime;
    delete om_ni;

    Close();
    FSOUND_Close();
}
/* --------------------------------------------------------------------------------------------- */

/* ----------------------- song control -------------------------------------------------------- */
bool Player::Open(const QString& songFilename)
{
    // if necessary, closes the current song
    if( !Close() )
    {
        qDebug("Unable to close the current stream");
        return false;
    }

    song = CoreIO::OpenSong( songFilename );
    activeStream = FSOUND_Stream_Open( song->songFilename.toStdString().c_str(), FSOUND_MPEGACCURATE, 0, 0 );

    if( activeStream == NULL )
    {
        qDebug( "FSOUND_Stream_Open failed on song %s", songFilename.toStdString().c_str() );
        activeStream = 0;
        return false;
    }

    if( song->length == 0 )
    {   // this is a freshly created Song object, so we must complete its content
        song->length = FSOUND_Stream_GetLengthMs( activeStream );
        song->segments[0].end = song->length;
    }
    segmentPlaying = -1;
    firstSynchronization = true;

    FSOUND_DSPUNIT* activeDsp = dsp_create( activeStream, 20 );
    FSOUND_DSP_SetActive( activeDsp, TRUE );
    return true;
}

bool Player::Close()
{
    if( activeStream != 0 )   // there is another song opened
    {
        if( synchronizing )
        {
            EndSynchronize();
        }

        if( FSOUND_Stream_Close( activeStream) == TRUE )
        {
            activeStream = 0;
            activeChannel = -1;
            tempo = 1.0;
            pitch = 0;
            segmentPlaying = -1;
            playing = false;
            firstPlay = true;
            synchronizing = false;
            delayedSynchronization = false;
            return true;
        }
        else
        {
            return false;
        }
    }
    return true;
}

bool Player::Pause()
{
    if( activeStream == 0 || synchronizing ) return false;

    if( playing )
    {
        FSOUND_SetPaused( activeChannel, TRUE );
        delayedSynchronization = false;
        playing = false;
    }
    return true;
}

bool Player::Play()
{
    if( activeStream == 0 || synchronizing ) return false;

    if(firstPlay)
    {
        firstPlay = false;
        tempo = 1.0;
        pitch = 0;
        FSOUND_Stream_SetEndCallback( activeStream, callback, this );
        activeChannel = FSOUND_Stream_Play( 0, activeStream );
        FSOUND_SetMute( activeChannel, TRUE );
        FSOUND_SetVolume( activeChannel, vol );
        playing = true;
        Synchronize();
        return true;
    }else
    {
        if(!playing)
        {
            FSOUND_SetPaused( activeChannel, FALSE );
            playing = true;
            if( delayedSynchronization || segmentPlaying == -1 )
            {
                Synchronize();
            }
        }
        return true;
    }
    return false;
}

bool Player::IsPlaying( void )
{
    return ( activeStream != 0 && playing );
}

bool Player::Stop()
{
    if( activeStream == 0 || synchronizing ) return false;

    if( playing ) Pause();
    FSOUND_Stream_SetTime( activeStream, 0 );
    segmentPlaying = -1;
    playing = false;
    return true;
}

bool Player::SetTime( int msecs )
{
    if( activeStream == 0 || synchronizing ) return false;
    if ( msecs < 0 || msecs > song->length ) return false;
    FSOUND_Stream_SetTime( activeStream, msecs );

    Synchronize();
    return true;
}

int Player::GetTime( void )
{
    if( activeStream == 0 ) return -1;
    if( synchronizing ) return msecsStart;
    int ms = FSOUND_Stream_GetTime( activeStream );
    if( ms == 0) return -1;
    return ms;
}

// vol is in [0, 1] range
void Player::SetVolume( float _vol )
{
    if( activeStream == 0 || synchronizing ) return;
    vol = _vol * 255;
    FSOUND_SetVolume( activeChannel, vol );
}

void Player::SetPlaybackMode( bool followSegments )
{
    normalPlayback = !followSegments;
}

int Player::GoToNextSegment( void )
{
    if( activeStream == 0 ) return -1;
    int nextSegment = segmentPlaying + 1;
    if( nextSegment == song->segments.size() ) nextSegment = 0;
    SetTime( song->segments[ nextSegment ].start );
    return nextSegment;
}

int Player::GoToPreviousSegment( void )
{
    if( activeStream == 0 ) return -1;
    int prevSegment = segmentPlaying - 1;
    if( prevSegment == -1 ) prevSegment = song->segments.size() - 1;
    SetTime( song->segments[ prevSegment ].start );
    return prevSegment;
}

void Player::Synchronize( int downVolTime, int upVolTime, bool customSynchronization,
                          bool forceSynchronization )
{
    // finds the segment we're playing
    if( synchronizing ) return;
    if( !playing && !forceSynchronization )
    {
        delayedSynchronization = true;
        return;
    }

    int segmentIndex = song->GetSegmentIndexAtTime( FSOUND_Stream_GetTime( activeStream ) );

    bool mustNotify = false;

    if( segmentIndex != -1 )
    {
        mustNotify = ( segmentPlaying != segmentIndex );
        segmentPlaying = segmentIndex;

        if( !normalPlayback && song->segments[ segmentIndex ].enabled )
        {
            newTempo = song->segments[ segmentIndex ].tempo;
        }
        else
        {
            newTempo = 1.0;
        }

        if( !normalPlayback && song->segments[ segmentIndex ].enabled )
        {
            intNewPitch = song->segments[ segmentIndex ].pitch;
        }
        else
        {
            intNewPitch = 0;
        }

        if( tempo == newTempo && pitch == intNewPitch && !forceSynchronization )
        {
            if( mustNotify )
            {
                for( int i=0; i<listeners.size(); i++ )
                {
                    listeners[i]->OnSegmentChange( segmentPlaying );
                }
            }

            if( firstSynchronization )
            {
                firstSynchronization = false;
                FSOUND_SetMute( activeChannel, FALSE );
            }

            return; // nothing to synchronize
        }

        if( mustNotify || !( tempo == newTempo && pitch == intNewPitch ) )
        {
            for( int i=0; i<listeners.size(); i++ )
            {
                listeners[i]->OnSegmentChange( segmentPlaying );
            }
        }

        volUpTime = upVolTime;
        newPitch = CalculatePitch( newTempo, intNewPitch );
        customSync = customSynchronization;
        msecsStart = FSOUND_Stream_GetTime( activeStream );
        syncDone = false;
        synchronizing = true;
        vol = FSOUND_GetVolume( activeChannel );
        if( msecsStart > 100 )
        {
            volAnime->SetDuration( downVolTime );
        }
        else
        {
            volAnime->SetDuration( 10 );
        }
        animeGraph.Get( volId )->SetMaxY( vol );
        volAnime->Stop();
        volAnime->Play();
    }
}

bool Player::IsSynchronizing( void )
{
    return synchronizing;
}

void Player::EndSynchronize( void )
{
    while( !syncDone ); // waits for the synchronization to finish

    if( msecsStart < 100 )
    {
        FSOUND_Stream_SetTime( activeStream, 0 );
        volAnime->SetDuration( 10 );
    }
    else
    {
        FSOUND_Stream_SetTime( activeStream, msecsStart - volUpTime * newTempo );
        volAnime->SetDuration( volUpTime );
    }

    if( firstSynchronization )
    {
        firstSynchronization = false;
        FSOUND_SetMute( activeChannel, FALSE );
    }
    volAnime->Reverse( true );
    volAnime->Play();
}
/* --------------------------------------------------------------------------------------------- */

/* ------------------------------- listeners handling ------------------------------------------ */
void Player::AddPlayerListener( PlayerListener* pl )
{
    listeners.append( pl );
}

void Player::RemovePlayerListener( PlayerListener* pl )
{
    listeners.removeOne( pl );
}

void Player::ClearListeners( void )
{
    listeners.clear();
}
/* --------------------------------------------------------------------------------------------- */

/* ------------------- AnimationObserver implementation ---------------------------------------- */
void Player::OnPause(Animation* a){}
void Player::OnStop(Animation* a){}
void Player::OnEnd(Animation* a)
{
    if( !a->IsReversed() )
    {   // volume is zero
        sleeper->Sleep();
        FSOUND_SetFrequency( activeChannel, 44100 * newTempo );
        set_pitch( newPitch );

        tempo = newTempo;
        pitch = intNewPitch;
    }
    else
    {
        syncDone = true;
        synchronizing = false;
    }
}

void Player::OnPlay(Animation* a){}
/* --------------------------------------------------------------------------------------------- */

/* ----------------------- other functions ----------------------------------------------------- */
float Player::CalculatePitch( float t, int p )
{
    return 1.0/t * exp(k * p / 12);
}

void Player::DoTask()
{
    int t = FSOUND_Stream_GetTime( activeStream );
    if ( t == 0 || activeStream == 0 ) return;

    // checks for the end of a loop
    Loop* l = song->GetFinishedLoop( t );
    if( l != 0 && playing && loopsTracking )
    {   // we have to loop
        SetTime( l->start );
    }

    if( !normalPlayback )
    {
        int offset = 0;

        // checks for the end of the segment
        int s = song->GetSegmentIndexAtTime( t, &offset );
        if( s != segmentPlaying && offset > -50 && offset < 50 )
        {
            if( song->segments[ segmentPlaying ].active )
            {   // loop active, we rewind at the beginning of the segment
                SetTime( song->segments[ segmentPlaying ].start );
            }
            else
            {   // synchronize to the next segment
                Synchronize();
            }
            return;
        }
    }
}

void Player::SyncDone( void )
{
    syncDone = true;
    if( !customSync )
    {
        EndSynchronize();
    }
}
/* --------------------------------------------------------------------------------------------- */

/* ========================== Synchronizer thread ============================================== */
Player::SleeperThread::SleeperThread( Player* _owner )
{
    owner = _owner;
    end = false;
}

void Player::SleeperThread::run( void )
{
    while( !end )
    {
        m.lock();
        c.wait( &m );

        msleep( 500 );
        owner->SyncDone();

        m.unlock();
    }
}

void Player::SleeperThread::Sleep( )
{
    c.wakeAll();
}

void Player::SleeperThread::StopIt( void )
{
    end = true;
    c.wakeAll();
}
/* ============================================================================================= */
